LevelStorage.decrease   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
1
import { event, select } from 'd3-selection';
2
import { DependencyNode, TreeNode } from '../../components/types';
3
import {
4
    Colors,
5
    ElementIds,
6
    FAST_TRANSITION_DURATION,
7
    MAXIMUM_ZOOM_SCALE,
8
    MINIMUM_ZOOM_SCALE,
9
    TRANSITION_DURATION,
10
    ZOOM_DECREASE,
11
    ZOOM_INCREASE,
12
} from '../AppConsts';
13
import { zoom, zoomIdentity } from 'd3-zoom';
14
import {
15
    selectAllNodes,
16
    selectDetailsButtonWrapper,
17
    selectDetailsExitButtonWrapper,
18
    selectHighLightedNodes,
19
    selectOverviewContainer,
20
    selectTooltip,
21
    selectTooltipBackground,
22
    selectTooltipText,
23
} from './Selectors';
24
import { initializeDetailsView, shutdownDetailsView } from '../../details/details-container';
25
import { hideHighlightBackground, zoomToHighLightedNodes } from '../../overview/highlight-background/highlight-background.helpers';
26
import { findMaxDependencyLevel, getHighLightedLabelColor } from '../../overview/graph-nodes';
27
import { centerScreenToDimension, findGroupBackgroundDimension, highlightNodes } from '../../overview/overview.helpers';
28
import { changeZoom } from '../../zoom/zoom';
29
30
enum Subscriptions {
31
    HIGHLIGHT = 'click.highlight',
32
    RESET_HIGHLIGHT = 'click.resetHighlight',
33
    CHANGE_HIGHLIGHT_RANGE = 'keydown.changeHighlightRange',
34
    ZOOM_ON_ARROW_KEY = 'keydown.zoom',
35
    OPEN_DETAILS = 'click.openDetails',
36
    CLOSE_DETAILS = 'click.closeDetails',
37
    SHOW_TOOLTIP = 'mouseover.tooltip',
38
    HIDE_TOOLTIP = 'mouseout.tooltip',
39
}
40
41
export function subscribeToHighlight() {
42
    selectAllNodes().on(Subscriptions.HIGHLIGHT, (node: DependencyNode) => {
43
        LevelStorage.reset();
44
        if (node.links.length) {
45
            highlightNodes(node);
46
            zoomToHighLightedNodes();
47
        }
48
        event.stopPropagation();
49
    });
50
}
51
52
export function subscribeToChangeHighlightRangeOnArrowKey() {
53
    select('body').on(Subscriptions.CHANGE_HIGHLIGHT_RANGE, () => {
54
        if (event.code === 'ArrowRight' || event.code === 'ArrowLeft') {
55
            const labelNodesGroup = select<SVGGElement, DependencyNode>('g#labels');
56
            LevelStorage.setMaxLevel(findMaxDependencyLevel(labelNodesGroup));
57
58
            if (!isFinite(LevelStorage.getMaxLevel())) {
59
                return;
60
            }
61
62
            if (LevelStorage.isBelowMax() && event.code === 'ArrowRight') {
63
                LevelStorage.increase();
64
            }
65
66
            if (LevelStorage.isAboveMin() && event.code === 'ArrowLeft') {
67
                LevelStorage.decrease();
68
            }
69
70
            // TODO refactor it to share logic with GraphHelpers/highlight function
71
            labelNodesGroup
72
                .selectAll<HTMLElement, DependencyNode>('g')
73
                .filter((node: DependencyNode) => node.level > 0)
74
                .each(function(this: HTMLElement, node: DependencyNode) {
75
                    const labelElement = this.firstElementChild;
76
                    const textElement = this.lastElementChild;
77
78
                    if (!labelElement || !textElement) {
79
                        return;
80
                    }
81
82
                    let labelColor = Colors.LIGHT_GREY;
83
                    let textColor = Colors.BASIC_TEXT;
84
                    if (node.level - 1 <= LevelStorage.getLevel()) {
85
                        labelColor = getHighLightedLabelColor(node);
86
                        textColor = Colors.WHITE;
87
                    }
88
89
                    select<Element, DependencyNode>(labelElement).attr('fill', labelColor);
90
                    select<Element, DependencyNode>(textElement).attr('fill', textColor);
91
                });
92
93
            zoomToHighLightedNodes();
94
        }
95
    });
96
}
97
98
export function subscribeToResetHighlight() {
99
    selectOverviewContainer().on(Subscriptions.RESET_HIGHLIGHT, () => {
100
        const highlightedNodes = selectHighLightedNodes();
101
        if (highlightedNodes.data().length) {
102
            selectAllNodes().each((node: DependencyNode) => (node.level = 0));
103
104
            const dimension = findGroupBackgroundDimension(highlightedNodes.data());
105
106
            highlightedNodes.each(function(this: SVGGElement) {
107
                const labelElement = this.firstElementChild;
108
                const textElement = this.lastElementChild;
109
110
                if (!labelElement || !textElement) {
111
                    return;
112
                }
113
114
                select<Element, DependencyNode>(labelElement).attr('fill', Colors.LIGHT_GREY);
115
                select<Element, DependencyNode>(textElement).attr('fill', Colors.BASIC_TEXT);
116
            });
117
118
            hideHighlightBackground();
119
120
            centerScreenToDimension(dimension, 1);
121
        }
122
    });
123
}
124
125
export function subscribeToZoomOnArrowKey() {
126
    select('body').on(Subscriptions.ZOOM_ON_ARROW_KEY, () => {
127
        switch (event.code) {
128
            case 'ArrowUp': {
129
                const currentScaleValue = ZoomScaleStorage.getScale();
130
                const newScaleValue = currentScaleValue * ZOOM_INCREASE;
131
                if (newScaleValue <= MAXIMUM_ZOOM_SCALE) {
132
                    ZoomScaleStorage.setScale(newScaleValue);
133
                    const container = selectOverviewContainer();
134
                    container.call(() => {
135
                        return zoom<any, any>()
136
                            .on('zoom', changeZoom(ElementIds.OVERVIEW_ZOOM))
137
                            .scaleBy(container, ZOOM_INCREASE);
138
                    }, zoomIdentity);
139
                }
140
                break;
141
            }
142
            case 'ArrowDown': {
143
                const currentScaleValue = ZoomScaleStorage.getScale();
144
                const newScaleValue = currentScaleValue * ZOOM_DECREASE;
145
                if (newScaleValue >= MINIMUM_ZOOM_SCALE) {
146
                    ZoomScaleStorage.setScale(newScaleValue);
147
                    const container = selectOverviewContainer();
148
                    container.call(() => {
149
                        return zoom<any, any>()
150
                            .on('zoom', changeZoom(ElementIds.OVERVIEW_ZOOM))
151
                            .scaleBy(container, ZOOM_DECREASE);
152
                    }, zoomIdentity);
153
                }
154
                break;
155
            }
156
        }
157
    });
158
}
159
160
export function subscribeToOpenDetails(detailsNodes: TreeNode[]) {
161
    selectDetailsButtonWrapper().on(Subscriptions.OPEN_DETAILS, () => {
162
        if (selectHighLightedNodes().data().length === 0) {
163
            return;
164
        }
165
        event.stopPropagation();
166
        const selectedNode = selectAllNodes()
167
            .data()
168
            .find(node => node.level === 1);
169
        if (selectedNode) {
170
            const selectedTreeNode = detailsNodes.find(treeNode => treeNode.name === selectedNode.name);
171
            if (selectedTreeNode) {
172
                initializeDetailsView(selectedTreeNode);
173
            }
174
        }
175
    });
176
}
177
178
export function subscribeToCloseDetails() {
179
    selectDetailsExitButtonWrapper().on(Subscriptions.CLOSE_DETAILS, () => {
180
        shutdownDetailsView();
181
    });
182
}
183
184
const TOOLTIP_PADDING = 10;
185
186
export function subscribeToShowTooltipOnNodeHover() {
187
    selectAllNodes()
188
        .on(Subscriptions.SHOW_TOOLTIP, function(node) {
189
            const { x = 0, y = 0 } = node;
190
            const tooltipText = selectTooltipText();
191
            const tooltipBackground = selectTooltipBackground();
192
            selectTooltip()
193
                .transition()
194
                .duration(FAST_TRANSITION_DURATION)
195
                .style('opacity', 0.9);
196
            tooltipText
197
                .text(node.version)
198
                .attr('x', x)
199
                .attr('y', y - 25 - TOOLTIP_PADDING);
200
            const { width, height } = tooltipText.node() ? tooltipText.node()!.getBBox() : { width: 0, height: 0 };
201
            tooltipBackground
202
                .attr('x', x - TOOLTIP_PADDING)
203
                .attr('y', y - 42 - TOOLTIP_PADDING)
204
                .attr('width', width + 2 * TOOLTIP_PADDING)
205
                .attr('height', height + TOOLTIP_PADDING);
206
        })
207
        .on(Subscriptions.HIDE_TOOLTIP, function() {
208
            selectTooltip()
209
                .transition()
210
                .duration(TRANSITION_DURATION)
211
                .style('opacity', 0);
212
        });
213
}
214
215
class LevelStorage {
216
    private static level: number = 1;
217
    private static maxLevel: number = 1;
218
219
    public static getLevel(): number {
220
        return this.level;
221
    }
222
223
    public static increase() {
224
        this.level = this.level + 1;
225
    }
226
227
    public static decrease() {
228
        this.level = this.level - 1;
229
    }
230
231
    public static isBelowMax() {
232
        return this.level < this.maxLevel;
233
    }
234
235
    static isAboveMin() {
236
        return this.level > 1;
237
    }
238
239
    static setMaxLevel(maxLevel: number) {
240
        this.maxLevel = maxLevel;
241
    }
242
243
    static getMaxLevel(): number {
244
        return this.maxLevel;
245
    }
246
247
    public static reset() {
248
        this.level = 1;
249
        this.maxLevel = 1;
250
    }
251
}
252
253
export class ZoomScaleStorage {
254
    private static currentScale = 1;
255
256
    public static setScale(newScale: number) {
257
        this.currentScale = newScale;
258
    }
259
260
    public static getScale() {
261
        return this.currentScale;
262
    }
263
}
264